﻿using System;
using Fine.Collections.Const;
using Fine.Collections.Intf;
using Fine.TestUtils;
using Fine.Utils.Structs;
using NUnit.Framework;

namespace Fine.Collections.Cont
{
    [TestFixture]
    public class BplusMap_test
    {

        #region TEST INCLUDE
        
        [Test]
        public void Test_Include_1()
        {
            var b = new BplusMap<ulong, string>();

            b.Count                     ._equals_(0);
            b.Height                    ._in_(0, 1);
            b.IsEmpty                   ._true_();
            b.IsNotEmpty                ._false_();

            b.Include(333uL, "333");

            b.FirstTerm.count           ._equals_(1);
            b.FirstTerm.keys[0]         ._equals_(333uL);
            b.FirstTerm.values[0]       ._equals_("333");
            (b.FirstTerm == b.LastTerm) ._true_();

            b.Count                     ._equals_(1);
            b.Height                    ._equals_(1);
            b.IsEmpty                   ._false_();
            b.IsNotEmpty                ._true_();
        }


        [Test]
        public void Test_Include_BlockSize()
        {
            var b = new BplusMap<ulong, string>(blockSize: 4);

            for (ulong i = 1; i <= 4; i++)
                b.Include(i, i.ToString());

            b.FirstTerm.count           ._equals_(4);
            (b.FirstTerm == b.LastTerm) ._true_();

            b.Count                     ._equals_(4);
            b.Height                    ._equals_(1);
            b.IsEmpty                   ._false_();
            b.IsNotEmpty                ._true_();

            for (int i = 0; i < 4; i++)
            {
                b.FirstTerm.keys[i]     ._equals_((ulong) (i + 1));
                b.FirstTerm.values[i]   ._equals_((i + 1).ToString());
            }
        }

        [TestCase(4, 7)]
        [TestCase(4, 16)]
        [TestCase(4, 17)]
        [TestCase(4, 100)]
        public void Test_Include_Several(int blockSize, int count)
        {
            var b = new BplusMap<int, string>(blockSize: blockSize);

            for (int i = 1; i <= count; i++)
                b.Include(i, i.ToString());

            b.Count._equals_(count);
            b.FirstKey._equals_(1);
            b.LastKey._equals_(count);

            b.KeysArray()._array_(new IntInterval(1, count).ToArray(i => i));
            b.ValuesArray()._array_(new IntInterval(1, count).ToArray(i => i.ToString()));

            b.VerifyInternalState();
        }


        [TestCase(4, 3)]
        [TestCase(4, 7)]
        [TestCase(4, 16)]
        [TestCase(4, 17)]
        public void Test_Include_Reverse(int blockSize, int count)
        {
            var b = new BplusMap<int, string>(blockSize: blockSize);

            for (int i = count; i > 0; i--)
                b.Include(i, i.ToString());

            b.Count._equals_(count);
            b.FirstKey._equals_(1);
            b.LastKey._equals_(count);

            b.KeysArray()._array_(new IntInterval(1, count).ToArray(i => i));
            b.ValuesArray()._array_(new IntInterval(1, count).ToArray(i => i.ToString()));

            b.VerifyInternalState();
        }


        [Test]
        [ExpectedException(typeof (DuplicatesFoundException))]
        public void Test_Duplicates_1()
        {
            var b = new BplusMap<string, string>();

            b.Include("a" + "aa", "xxx");
            b.Include("aa" + "a", "yyy");
        }

        [Test]
        [ExpectedException(typeof (DuplicatesFoundException))]
        public void Test_Duplicates_3()
        {
            var b = new BplusMap<string, string>();

            b.Include("aaa", "xxx");
            b.Include("bbb", "xxx");
            b.Include("ccc", "xxx");

            b.Include("bbb", "yyy");
        }

        #endregion



        #region TEST SETTER

        [Test]
        public void Test_Set_NewOne()
        {
            var b = new BplusMap<ulong, string>();

            b.Count              ._equals_(0);
            b.ContainsKey(777uL) ._false_();

            b[777uL] = "777";

            b.Count              ._equals_(1);
            b.ContainsKey(777uL) ._true_();

            b[777uL]._equals_("777");
        }


        [Test]
        public void Test_Set_ExistentOne()
        {
            var b = new BplusMap<ulong, string>();

            b.Include(777uL, "лабуда");

            b.Count           ._equals_(1);

            b[777uL] = "777";

            b.Count           ._equals_(1);

            b[777uL]          ._equals_("777");
        }

        #endregion



        #region TEST EXCLUDE

        [Test]
        public void Test_Exclude_SingleBlock_First()
        {
            var b = new BplusMap<long, string>(4);
            b.Include(1L, "1");
            b.Include(2L, "2");
            b.Include(3L, "3");

            var ex = b.Exclude(1L);

            ex               ._equals_("1");
            b.Count          ._equals_(2);
            b.KeysArray()    ._array_(2L, 3L);
            b.ValuesArray()  ._array_("2", "3");

            b.VerifyInternalState();
        }


        [Test]
        public void Test_Exclude_SingleBlock_Middle()
        {
            var b = new BplusMap<long, string>(4);
            b.Include(1L, "1");
            b.Include(2L, "2");
            b.Include(3L, "3");

            var ex = b.Exclude(2L);

            ex               ._equals_("2");
            b.Count          ._equals_(2);
            b.KeysArray()    ._array_(1L, 3L);
            b.ValuesArray()  ._array_("1", "3");

            b.VerifyInternalState();
        }


        [Test]
        public void Test_Exclude_SingleBlock_Last()
        {
            var b = new BplusMap<long, string>(4);
            b.Include(1L, "1");
            b.Include(2L, "2");
            b.Include(3L, "3");

            var ex = b.Exclude(3L);

            ex               ._equals_("3");
            b.Count          ._equals_(2);
            b.KeysArray()    ._array_(1L, 2L);
            b.ValuesArray()  ._array_("1", "2");

            b.VerifyInternalState();
        }


        [Test]
        public void Test_Exclude_SingleBlock_LastLast()
        {
            var b = new BplusMap<long, string>(4);
            b.Include(1L, "1");
            b.Include(2L, "2");
            b.Include(3L, "3");
            b.Include(4L, "4");   // the last possible entry in the block

            var ex = b.Exclude(4L);

            ex               ._equals_("4");
            b.Count          ._equals_(3);
            b.KeysArray()    ._array_(1L, 2L, 3L);
            b.ValuesArray()  ._array_("1", "2", "3");

            b.VerifyInternalState();
        }


        [TestCase(1)]
        [TestCase(5)]
        [TestCase(9)]
        public void Test_Exclude_SingleBlock_Nothing(long x)
        {
            var b = new BplusMap<long, string>(4);
            b.Include(3L, "3");
            b.Include(7L, "7");
            b.Include(8L, "8");

            var ex = b.Exclude(x);

            ex       ._is_null_();
            b.Count  ._equals_(3);

            b.VerifyInternalState();
        }


        [Test]
        public void Test_Exclude_SingleBlock_TheOnly()
        {
            var b = new BplusMap<long, string>(4);
            b.Include(666L, "666");

            var ex = b.Exclude(666L);

            ex               ._equals_("666");
            b.Count          ._equals_(0);

            b.VerifyInternalState();
        }


        [Test]
        public void Test_Exclude_SingleBlock_All()
        {
            var b = new BplusMap<long, string>(4);
            b.Include(1L, "1");
            b.Include(2L, "2");
            b.Include(3L, "3");
            b.Include(4L, "4");   // the last possible entry in the block

            for (int i = 1; i <= 4; i++)
            {
                string ex = b.Exclude(i);

                ex    ._equals_(i.ToString());

                b.VerifyInternalState();
            }
        }


        [Test]
        public void Test_Exclude_Many_Odd()
        {
            var b = new BplusMap<long, long>(4);
            for (long k = 1; k <= 33; k++)
                b.Include(k,k);

            // TEST
            var seq = CC1.ListOf<long>(23, 31, 3, 33, 13, 15, 25, 7, 19, 11, 5, 9, 29, 27, 17, 1);
            foreach(long x in seq)
            {
                var ex = b.Exclude(x);
                ex ._equals_(x);
                b.VerifyInternalState();
            }
        }


        #endregion



        #region TEST ITERATOR

        [Test]
        public void Test_MapIterator_Sequentially_OneBlock()
        {
            var map = new BplusMap<uint,string>(4);
            map.Include(1111u, "1111");
            map.Include(2222u, "2222");
            map.Include(3333u, "3333");
            map.Include(4444u, "4444");

            var it = map.TakeIterator();

            it.Next()         ._true_();  
            it.CurrentKey     ._equals_(1111u);
            it.Current.Key    ._equals_(1111u);
            it.CurrentValue   ._equals_("1111");
            it.Current.Value  ._equals_("1111");
            it.Next()         ._true_();  
            it.CurrentKey     ._equals_(2222u);
            it.Current.Key    ._equals_(2222u);
            it.CurrentValue   ._equals_("2222");
            it.Current.Value  ._equals_("2222");
            it.Next()         ._true_();  
            it.CurrentKey     ._equals_(3333u);
            it.Current.Key    ._equals_(3333u);
            it.CurrentValue   ._equals_("3333");
            it.Current.Value  ._equals_("3333");
            it.Next()         ._true_();  
            it.CurrentKey     ._equals_(4444u);
            it.Current.Key    ._equals_(4444u);
            it.CurrentValue   ._equals_("4444");
            it.Current.Value  ._equals_("4444");

            it.Next()         ._false_();
        }


        [Test]
        public void Test_MapIterator_Sequentially_SeveralBlocks()
        {
            var map = MakeMap60(pass);
            var it = map.TakeIterator();

            for (uint i = 1u; i <= 60u; i++)
            {
                it.Next()       ._true_();
                it.CurrentKey   ._equals_(i);
                it.CurrentValue ._equals_(i.ToString());
            }

            it.Next()           ._false_();
        }


        [Test]
        public void Test_MapIterator_Jump_Basic()
        {
            var map = MakeMap60(pass);
            var it = map.TakeIterator();

            it.Jump(25u)    ._equals_(JumpResult.Exactly);
            it.CurrentKey   ._equals_(25u);
            it.Jump(26u)    ._equals_(JumpResult.Exactly);
            it.CurrentKey   ._equals_(26u);
            it.Jump(55u)    ._equals_(JumpResult.Exactly);
            it.CurrentKey   ._equals_(55u);
            it.Jump(100u)   ._equals_(JumpResult.End);
        }


        [Test]
        public void Test_MapIterator_Jump_Backward()
        {
            var map = MakeMap60(pass);
            var it = map.TakeIterator();
            it.Next();
            it.Next();
            it.Next();
            it.Jump(1u)    ._equals_(JumpResult.Before);
            it.CurrentKey  ._equals_(3u);
        }


        [Test]
        public void Test_MapIterator_Jump_Over()
        {
            var map = MakeMap60(x => x%5u == 0u);
            var it = map.TakeIterator();
            it.Next();  // 5
            it.Jump(22u)   ._equals_(JumpResult.Over);
            it.CurrentKey  ._equals_(25u);
            it.Jump(33u)   ._equals_(JumpResult.Over);
            it.CurrentKey  ._equals_(35u);
        }


        private static BplusMap<uint,string> MakeMap60(Predicate<uint> predicate)
        {                            
            var map = new BplusMap<uint,string>(4);
            for (uint i = 1u; i <= 60u; i++)
                if (predicate(i))
                    map.Include(i, i.ToString());
            return map;
        }

        #endregion



        private static readonly Predicate<uint> pass = o => true;
    }
}
